#include "fyp/render/LoadingPolicy.h"

//------------------------------------------------------------------------------
// additional header includes
#include <d3d9.h>
#include <d3dx9.h>
#include <strsafe.h>
//#include <shlwapi.h>
#include <string>
#include <vector>
#include <map>

#include "fyp/render/RenderedTexture.h"

//------------------------------------------------------------------------------
namespace ming
{
namespace fyp
{
namespace render
{
	//--------------------------------------------------------------------------
	TBasicLoadingPolicy::TBasicLoadingPolicy(IDirect3DDevice9 *device)
	{
		mDevice = device;
		mDevice->AddRef();
	}

	//--------------------------------------------------------------------------
	TBasicLoadingPolicy::~TBasicLoadingPolicy()
	{
		mDevice->Release();
	}

	//--------------------------------------------------------------------------
	bool TD3DXMeshLoadingPolicy::msExportAscii = false;

	ID3DXMesh* TD3DXMeshLoadingPolicy::Load(
		const char *filename,
		const Any &params)
	{
		ID3DXMesh* mesh = NULL;

		WCHAR wFileName[MAX_PATH] = L"";
        StringCchPrintfW(wFileName, MAX_PATH, L"%S", filename);
		
		if(FAILED( D3DXLoadMeshFromX(
			wFileName,
			D3DXMESH_MANAGED,
			mDevice,
			NULL,
			NULL,
			NULL,
			NULL,
			&mesh) ) )
			return NULL;

		ProcessMesh(mesh);

		if(msExportAscii)
		{
			std::wstring inputName(wFileName);
			std::wstring::size_type loc = inputName.find_last_of(L'.');
			std::wstring outputName = inputName.substr(0, loc) + L".ascii.x";

			HRESULT hr = D3DXSaveMeshToX(outputName.c_str(), mesh, NULL, NULL, NULL, 0, D3DXF_FILEFORMAT_TEXT);

			hr;
		}

		return mesh;
	}

	//--------------------------------------------------------------------------
	void TD3DXMeshLoadingPolicy::ProcessMesh(ID3DXMesh *&mesh)
	{
		DWORD *rgdwAdjacency = NULL;
		ID3DXMesh* tempMesh;

		rgdwAdjacency = new DWORD[mesh->GetNumFaces() * 3];
		if( rgdwAdjacency == NULL )
			return;

		// change FVF
		D3DVERTEXELEMENT9 vertexDecl[] = {
			{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
			{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
			{0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
			{0, 32, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0},
			{0, 44, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
			D3DDECL_END()
		};

		mesh->CloneMesh(mesh->GetOptions(), vertexDecl, mDevice, &tempMesh);

		mesh->Release();
		mesh = tempMesh;

		// clean mesh
		mesh->GenerateAdjacency(1e-6f, rgdwAdjacency);
		
		DWORD *tempAdjacency = new DWORD[mesh->GetNumFaces() * 3];

		ID3DXBuffer *pErr;

		if(FAILED(D3DXCleanMesh(D3DXCLEAN_SIMPLIFICATION, mesh, rgdwAdjacency, &tempMesh, tempAdjacency, &pErr)))
			printf("%s\n", pErr->GetBufferPointer());

		mesh->Release();
		mesh = tempMesh;

		delete[] rgdwAdjacency;
		rgdwAdjacency = tempAdjacency;

		// compute normal & tangent
		D3DXComputeTangentFrameEx(
			mesh,
			D3DDECLUSAGE_TEXCOORD,
			0,
			D3DDECLUSAGE_TANGENT,
			0,
			D3DX_DEFAULT,
			0,
			D3DDECLUSAGE_NORMAL,
			0,
			D3DXTANGENT_CALCULATE_NORMALS | D3DXTANGENT_WRAP_UV | D3DXTANGENT_GENERATE_IN_PLACE | D3DXTANGENT_ORTHOGONALIZE_FROM_U,
			NULL, //rgdwAdjacency,
			-1.01f,
			-0.01f,
			-1.01f,
			NULL,
			NULL);

		// optimize mesh
		mesh->GenerateAdjacency(1e-6f, rgdwAdjacency);
		mesh->OptimizeInplace(D3DXMESHOPT_VERTEXCACHE, rgdwAdjacency, NULL, NULL, NULL);

		delete []rgdwAdjacency;
	}

	//--------------------------------------------------------------------------
	void TD3DXMeshLoadingPolicy::Unload(ID3DXMesh *mesh)
	{
		mesh->Release();
	}

	//--------------------------------------------------------------------------
	struct TD3DXInclude : public ID3DXInclude
	{
		std::vector<std::wstring> mPaths;

		//------------------------------
		std::wstring GetFileDir(const WCHAR *filePath)
		{
			std::wstring input(filePath);

			size_t i;
			for(i = input.length()-1; i>=0; --i)
				if(input[i] == L'\\' || input[i] == L'/')
					break;

			return input.substr(0, i);
		}

		//------------------------------
		bool FileExist(const WCHAR *filename)
		{
			return GetFileAttributes(filename) != INVALID_FILE_ATTRIBUTES;
		}

		//------------------------------
		bool Resolve(const WCHAR *relPath, WCHAR *absPath)
		{
			GetFullPathName(relPath, MAX_PATH, absPath, NULL);

			if( FileExist(absPath) )
			{
				mPaths.push_back( GetFileDir(absPath) );
			}
			else
			{
				std::wstring lhs(relPath);

				for(size_t i=0; i<mPaths.size(); ++i)
				{
					std::wstring testPath = mPaths[i] + L"\\" + lhs;

					if( FileExist( testPath.c_str() ) )
					{
						GetFullPathName(testPath.c_str(), MAX_PATH, absPath, NULL);
						return true;
					}

					return false;
				}
			}

			return true;
		}

		//------------------------------
		STDMETHOD(Open)(
			D3DXINCLUDE_TYPE IncludeType,
			LPCSTR pFileName,
			LPCVOID pParentData,
			LPCVOID *ppData,
			UINT *pBytes)
		{
			WCHAR relFilename[MAX_PATH] = L"";
			WCHAR absFilename[MAX_PATH] = L"";
			StringCchPrintfW(relFilename, MAX_PATH, L"%S", pFileName);

			if( !Resolve(relFilename, absFilename) )
				return E_FAIL;

			HANDLE fileHandle = CreateFile(
				absFilename,
				GENERIC_READ,
				FILE_SHARE_READ,
				NULL,
				OPEN_EXISTING,
				FILE_ATTRIBUTE_NORMAL,
				NULL);

			if(fileHandle == (HANDLE)0xffffffff)
				return E_FAIL;

			DWORD fileSize = GetFileSize(fileHandle, NULL);

			if(fileSize != 0)
			{
				BYTE *pBuf = new BYTE[fileSize];
				DWORD byteRead;
				ReadFile(fileHandle, pBuf, fileSize, &byteRead, NULL);

				if(byteRead == fileSize)
				{
					*ppData = pBuf;
					*pBytes = (UINT)byteRead;
				}
				else
					delete[] pBuf;
			}
			else
				return E_FAIL;

			CloseHandle(fileHandle);

			return S_OK;
		}

		//------------------------------
		STDMETHOD(Close)(LPCVOID pData)
		{
			if(pData != NULL)
				delete[] pData;

			return S_OK;
		}

		//------------------------------
	};

	//--------------------------------------------------------------------------
	ID3DXEffect* TD3DXEffectLoadingPolicy::Load(
		const char *filename,
		const Any &params)
	{
		ID3DXEffect *effect;
		ID3DXBuffer *err;

		TD3DXInclude incl;

		WCHAR filenameW[MAX_PATH] = L"";
        StringCchPrintfW(filenameW, MAX_PATH, L"%S", filename);

		DWORD flags = D3DXFX_NOT_CLONEABLE;

#ifdef SHADER_DEBUG
		flags |= D3DXSHADER_DEBUG;
#endif

		if( FAILED( D3DXCreateEffectFromFile(
			mDevice,
			filenameW,
			NULL,
			&incl,
			flags,
			NULL,
			&effect,
			&err) ) )
		{
			MessageBoxA(NULL, (char*)err->GetBufferPointer(), "Error loading effect", MB_OK);
			return NULL;
		}

		return effect;
	}

	//--------------------------------------------------------------------------
	void TD3DXEffectLoadingPolicy::Unload(ID3DXEffect *effect)
	{
		effect->Release();
	}

	//--------------------------------------------------------------------------
	IDirect3DTexture9* TD3DTextureLoadingPolicy::Load(const char *filename, const Any &params)
	{
		WCHAR filenameW[MAX_PATH] = L"";
        StringCchPrintfW(filenameW, MAX_PATH, L"%S", filename);

		IDirect3DTexture9 *texture = NULL;

		if( FAILED( D3DXCreateTextureFromFile(
			mDevice,
			filenameW,
			&texture) ) )
		{
			MessageBoxA(NULL, filename, "Failed to load texture", MB_OK);
			return NULL;
		}

		return texture;
	}

	//--------------------------------------------------------------------------
	void TD3DTextureLoadingPolicy::Unload(IDirect3DTexture9 *texture)
	{
		texture->Release();
	}

	//--------------------------------------------------------------------------
	IDirect3DTexture9* TD3DRenderedTextureLoadingPolicy::Load(const char *filename, const Any &params)
	{
		IDirect3DTexture9 *texture = NULL;

		tuple<size_t, size_t, size_t> createParam = 
			core::any_cast< tuple<size_t, size_t, size_t> >(params);
		
		if(FAILED(mDevice->CreateTexture(
			(UINT)get<1>(createParam),
			(UINT)get<2>(createParam),
			1,
			D3DUSAGE_RENDERTARGET,
			(D3DFORMAT)Convert2D3dFormat(get<0>(createParam)),
			D3DPOOL_DEFAULT,
			&texture,
			NULL)))
		{
			MessageBoxA(NULL, "", "Failed to create rendered texture", MB_OK);
			return NULL;
		}

		return texture;
		
	}

	//--------------------------------------------------------------------------
	void TD3DRenderedTextureLoadingPolicy::Unload(IDirect3DTexture9 *texture)
	{
		texture->Release();
	}

	//--------------------------------------------------------------------------
	unsigned long TD3DRenderedTextureLoadingPolicy::Convert2D3dFormat(size_t fmt)
	{
		std::map<size_t, unsigned long> mapping;

		mapping[IRenderedTexture::INT5_RGB]		= D3DFMT_R5G6B5;
		mapping[IRenderedTexture::INT5_XRGB]	= D3DFMT_X1R5G5B5;
		mapping[IRenderedTexture::INT5_ARGB]	= D3DFMT_A1R5G5B5;

		mapping[IRenderedTexture::INT8_ARGB]	= D3DFMT_A8R8G8B8;
		mapping[IRenderedTexture::INT8_XRGB]	= D3DFMT_X8R8G8B8;

		mapping[IRenderedTexture::INT16_ABGR]	= D3DFMT_A16B16G16R16;
		mapping[IRenderedTexture::INT16_GR]		= D3DFMT_G16R16;

		mapping[IRenderedTexture::FLOAT16_ABGR]	= D3DFMT_A16B16G16R16F;
		mapping[IRenderedTexture::FLOAT16_GR]	= D3DFMT_G16R16F;
		mapping[IRenderedTexture::FLOAT16_R]	= D3DFMT_R16F;

		mapping[IRenderedTexture::FLOAT32_ABGR]	= D3DFMT_A32B32G32R32F;
		mapping[IRenderedTexture::FLOAT32_GR]	= D3DFMT_G32R32F;
		mapping[IRenderedTexture::FLOAT32_R]	= D3DFMT_R32F;

		return mapping[fmt];
	}

	//--------------------------------------------------------------------------
	IDirect3DSurface9* TD3DDepthBufferLoadingPolicy::Load(const char *filename, const Any &params)
	{
		IDirect3DSurface9 *buf = NULL;

		tuple<size_t, size_t> createParam = 
			core::any_cast< tuple<size_t, size_t> >(params);

		if(FAILED(mDevice->CreateDepthStencilSurface(
			(UINT)get<0>(createParam),
			(UINT)get<1>(createParam),
			D3DFMT_D24S8,
			D3DMULTISAMPLE_NONE,
			0,
			FALSE,
			&buf,
			NULL)))
		{
			// todo: throw exception
			return NULL;
		}

		return buf;
	}

	//--------------------------------------------------------------------------
	void TD3DDepthBufferLoadingPolicy::Unload(IDirect3DSurface9 *buf)
	{
		buf->Release();
	}

	//--------------------------------------------------------------------------
}
}
}
